package woaini.wind.utils;

import io.minio.*;
import io.minio.http.Method;
import io.minio.messages.Bucket;
import io.minio.messages.DeleteObject;
import io.minio.messages.Item;
import lombok.experimental.UtilityClass;
import lombok.extern.slf4j.Slf4j;
import org.springframework.web.multipart.MultipartFile;
import woaini.wind.domain.MinIOConfig;
import woaini.wind.exception.BadConfigurationException;

import java.io.ByteArrayInputStream;
import java.io.InputStream;
import java.io.UnsupportedEncodingException;
import java.net.URLDecoder;
import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.LinkedList;
import java.util.List;
import java.util.Optional;

@Slf4j
@UtilityClass
public class MinIoUtil     {

  private static MinioClient minioClient;
  private static MinIOConfig minioProperties;

  public void initClient(MinIOConfig config){
//    log.info("开始创建 MinioClient...");
//    MinIoUtil.minioProperties = config;
//    minioClient = MinioClient
//      .builder()
//      .endpoint(minioProperties.getEndpoint())
//      .credentials(minioProperties.getAccessKey(), minioProperties.getSecretKey())
//      .build();
//    try {
//      MinIoUtil.createBucket(minioProperties.getBucketName());
//    } catch (Exception e) {
//      throw new BadConfigurationException("创建minIO存储失败");
//    }
//    log.info("创建完毕 MinioClient...");
  }

  public MinioClient getMinioClient(){
    if (minioClient == null){
      throw new BadConfigurationException("minIO存储未配置");
    }
    return minioClient;
  }


  /**
   * 获取上传文件前缀路径
   * @return
   */
  public String getBasisUrl() {
    return minioProperties.getEndpoint() + minioProperties.getSeparator() + minioProperties.getBucketName() + minioProperties.getSeparator();
  }

  /******************************  Operate Bucket Start  ******************************/

  /**
   * 启动SpringBoot容器的时候初始化Bucket
   * 如果没有Bucket则创建
   * @throws Exception
   */
  public void createBucket(String bucketName) throws Exception {
    if (!bucketExists(bucketName)) {
      getMinioClient().makeBucket(MakeBucketArgs.builder().bucket(bucketName).build());
      //修改桶策略为读写权限
      String READ_WRITE = "{\"Version\":\"2012-10-17\",\"Statement\":[{\"Effect\":\"Allow\",\"Principal\":{\"AWS\":[\"*\"]},\"Action\":[\"s3:GetBucketLocation\",\"s3:ListBucket\",\"s3:ListBucketMultipartUploads\"],\"Resource\":[\"arn:aws:s3:::" + bucketName + "\"]},{\"Effect\":\"Allow\",\"Principal\":{\"AWS\":[\"*\"]},\"Action\":[\"s3:DeleteObject\",\"s3:GetObject\",\"s3:ListMultipartUploadParts\",\"s3:PutObject\",\"s3:AbortMultipartUpload\"],\"Resource\":[\"arn:aws:s3:::" + bucketName + "/*\"]}]}";
      getMinioClient().setBucketPolicy(SetBucketPolicyArgs.builder().bucket(bucketName).config(READ_WRITE).build());
    }
  }

  /**
   *  判断Bucket是否存在，true：存在，false：不存在
   * @return
   * @throws Exception
   */
  public boolean bucketExists(String bucketName) throws Exception {
    return getMinioClient().bucketExists(BucketExistsArgs.builder().bucket(bucketName).build());
  }


  /**
   * 获得Bucket的策略
   * @param bucketName
   * @return
   * @throws Exception
   */
  public String getBucketPolicy(String bucketName) throws Exception {
    return getMinioClient()
      .getBucketPolicy(
        GetBucketPolicyArgs
          .builder()
          .bucket(bucketName)
          .build()
      );
  }


  /**
   * 获得所有Bucket列表
   * @return
   * @throws Exception
   */
  public List<Bucket> getAllBuckets() throws Exception {
    return getMinioClient().listBuckets();
  }

  /**
   * 根据bucketName获取其相关信息
   * @param bucketName
   * @return
   * @throws Exception
   */
  public Optional<Bucket> getBucket(String bucketName) throws Exception {
    return getAllBuckets().stream().filter(b -> b.name().equals(bucketName)).findFirst();
  }

  /**
   * 根据bucketName删除Bucket，true：删除成功； false：删除失败，文件或已不存在
   * @param bucketName
   * @throws Exception
   */
  public void removeBucket(String bucketName) throws Exception {
    getMinioClient().removeBucket(RemoveBucketArgs.builder().bucket(bucketName).build());
  }

  /******************************  Operate Bucket End  ******************************/


  /******************************  Operate Files Start  ******************************/

  /**
   * 判断文件是否存在
   * @param bucketName 存储桶
   * @param objectName 文件名
   * @return
   */
  public boolean isObjectExist(String bucketName, String objectName) {
    boolean exist = true;
    try {
      getMinioClient().statObject(StatObjectArgs.builder().bucket(bucketName).object(objectName).build());
    } catch (Exception e) {
      log.error("[Minio工具类]>>>> 判断文件是否存在, 异常：", e);
      exist = false;
    }
    return exist;
  }

  /**
   * 判断文件夹是否存在
   * @param bucketName 存储桶
   * @param objectName 文件夹名称
   * @return
   */
  public boolean isFolderExist(String bucketName, String objectName) {
    boolean exist = false;
    try {
      Iterable<Result<Item>> results = getMinioClient().listObjects(
        ListObjectsArgs.builder().bucket(bucketName).prefix(objectName).recursive(false).build());
      for (Result<Item> result : results) {
        Item item = result.get();
        if (item.isDir() && objectName.equals(item.objectName())) {
          exist = true;
        }
      }
    } catch (Exception e) {
      log.error("[Minio工具类]>>>> 判断文件夹是否存在，异常：", e);
      exist = false;
    }
    return exist;
  }

  /**
   * 根据文件前置查询文件
   * @param bucketName 存储桶
   * @param prefix 前缀
   * @param recursive 是否使用递归查询
   * @return MinioItem 列表
   * @throws Exception
   */
  public List<Item> getAllObjectsByPrefix(String bucketName,
                                          String prefix,
                                          boolean recursive) throws Exception {
    List<Item> list = new ArrayList<>();
    Iterable<Result<Item>> objectsIterator = getMinioClient().listObjects(
      ListObjectsArgs.builder().bucket(bucketName).prefix(prefix).recursive(recursive).build());
    if (objectsIterator != null) {
      for (Result<Item> o : objectsIterator) {
        Item item = o.get();
        list.add(item);
      }
    }
    return list;
  }

  /**
   * 获取文件流
   * @param bucketName 存储桶
   * @param objectName 文件名
   * @return 二进制流
   */
  public InputStream getObject(String bucketName, String objectName) throws Exception {
    return getMinioClient().getObject(GetObjectArgs.builder().bucket(bucketName).object(objectName).build());
  }

  /**
   * 断点下载
   * @param bucketName 存储桶
   * @param objectName 文件名称
   * @param offset 起始字节的位置
   * @param length 要读取的长度
   * @return 二进制流
   */
  public InputStream getObject(String bucketName, String objectName, long offset, long length)throws Exception {
    return getMinioClient().getObject(
      GetObjectArgs.builder()
        .bucket(bucketName)
        .object(objectName)
        .offset(offset)
        .length(length)
        .build());
  }

  /**
   * 获取路径下文件列表
   * @param bucketName 存储桶
   * @param prefix 文件名称
   * @param recursive 是否递归查找，false：模拟文件夹结构查找
   * @return 二进制流
   */
  public Iterable<Result<Item>> listObjects(String bucketName, String prefix,
                                            boolean recursive) {
    return getMinioClient().listObjects(
      ListObjectsArgs.builder()
        .bucket(bucketName)
        .prefix(prefix)
        .recursive(recursive)
        .build());
  }

  /**
   * 使用MultipartFile进行文件上传
   * @param bucketName 存储桶
   * @param file 文件名
   * @param objectName 对象名
   * @param contentType 类型
   * @return
   * @throws Exception
   */
  public ObjectWriteResponse uploadFile(String bucketName, MultipartFile file,
                                        String objectName, String contentType) throws Exception {
    InputStream inputStream = file.getInputStream();
    return getMinioClient().putObject(
      PutObjectArgs.builder()
        .bucket(bucketName)
        .object(objectName)
        .contentType(contentType)
        .stream(inputStream, inputStream.available(), -1)
        .build());
  }

  /**
   * 上传本地文件
   * @param bucketName 存储桶
   * @param objectName 对象名称（落盘名称 需要加后缀 如：xx.jpg）
   * @param fileName 本地文件路径
   */
  public ObjectWriteResponse uploadFile(String bucketName, String objectName,
                                        String fileName) throws Exception {
    return getMinioClient().uploadObject(
      UploadObjectArgs.builder()
        .bucket(bucketName)
        .object(objectName)
        .filename(fileName)
        .build());
  }

  /**
   * 通过流上传文件
   *
   * @param bucketName 存储桶
   * @param objectName 文件对象
   * @param inputStream 文件流
   */
  public ObjectWriteResponse uploadFile(String bucketName, String objectName, InputStream inputStream) throws Exception {
    return getMinioClient().putObject(
      PutObjectArgs.builder()
        .bucket(bucketName)
        .object(objectName)
        .stream(inputStream, inputStream.available(), -1)
        .build());
  }

  /**
   * 创建文件夹或目录
   * @param bucketName 存储桶
   * @param objectName 目录路径
   */
  public ObjectWriteResponse createDir(String bucketName, String objectName) throws Exception {
    return getMinioClient().putObject(
      PutObjectArgs.builder()
        .bucket(bucketName)
        .object(objectName)
        .stream(new ByteArrayInputStream(new byte[]{}), 0, -1)
        .build());
  }

  /**
   * 获取文件信息, 如果抛出异常则说明文件不存在
   *
   * @param bucketName 存储桶
   * @param objectName 文件名称
   */
  public String getFileStatusInfo(String bucketName, String objectName) throws Exception {
    return getMinioClient().statObject(
      StatObjectArgs.builder()
        .bucket(bucketName)
        .object(objectName)
        .build()).toString();
  }

  /**
   * 拷贝文件
   *
   * @param bucketName 存储桶
   * @param objectName 文件名
   * @param srcBucketName 目标存储桶
   * @param srcObjectName 目标文件名
   */
  public ObjectWriteResponse copyFile(String bucketName, String objectName,
                                      String srcBucketName, String srcObjectName) throws Exception {
    return getMinioClient().copyObject(
      CopyObjectArgs.builder()
        .source(CopySource.builder().bucket(bucketName).object(objectName).build())
        .bucket(srcBucketName)
        .object(srcObjectName)
        .build());
  }

  /**
   * 删除文件
   * @param bucketName 存储桶
   * @param objectName 文件名称
   */
  public void removeFile(String bucketName, String objectName) throws Exception {
    getMinioClient().removeObject(
      RemoveObjectArgs.builder()
        .bucket(bucketName)
        .object(objectName)
        .build());
  }

  /**
   * 批量删除文件
   * @param bucketName 存储桶
   * @param keys 需要删除的文件列表
   * @return
   */
  public void removeFiles(String bucketName, List<String> keys) {
    List<DeleteObject> objects = new LinkedList<>();
    keys.forEach(s -> {
      objects.add(new DeleteObject(s));
      try {
        removeFile(bucketName, s);
      } catch (Exception e) {
        log.error("[Minio工具类]>>>> 批量删除文件，异常：", e);
      }
    });
  }

  /**
   * 获取文件外链
   * @param bucketName 存储桶
   * @param objectName 文件名
   * @param expires 过期时间 <=7 秒 （外链有效时间（单位：秒））
   * @return url
   * @throws Exception
   */
  public String getPresignedObjectUrl(String bucketName, String objectName, Integer expires) throws Exception {
    GetPresignedObjectUrlArgs args = GetPresignedObjectUrlArgs.builder().expiry(expires).bucket(bucketName).object(objectName).build();
    return getMinioClient().getPresignedObjectUrl(args);
  }

  /**
   * 获得文件外链
   * @param bucketName
   * @param objectName
   * @return url
   * @throws Exception
   */
  public String getPresignedObjectUrl(String bucketName, String objectName) throws Exception {
    GetPresignedObjectUrlArgs args = GetPresignedObjectUrlArgs.builder()
      .bucket(bucketName)
      .object(objectName)
      .method(Method.GET).build();
    return getMinioClient().getPresignedObjectUrl(args);
  }

  /**
   * 将URLDecoder编码转成UTF8
   * @param str
   * @return
   * @throws UnsupportedEncodingException
   */
  public String getUtf8ByURLDecoder(String str) throws UnsupportedEncodingException {
    String url = str.replaceAll("%(?![0-9a-fA-F]{2})", "%25");
    return URLDecoder.decode(url, StandardCharsets.UTF_8);
  }

}
